package common;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;

/**
 * Abstract class defining common methods for all the repository classes
 * @author corentin
 * @param <T> Type of the entity managed by the repository
 */
public abstract class BaseRepository<T> {
	
	private static final String PERSISTENCE_UNIT_NAME = "HockeyPersistenceUnit";
	
	private EntityManager em = null;
	
	private Class<T> entityClass;
	
	public BaseRepository () {
		entityClass = determineGenericClass();
	}
	
	/**
	 * Get the {@link EntityManager}
	 * @return {@link EntityManager}
	 */
	protected EntityManager getEm() {
		if (em == null) {
			EntityManagerFactory emf = Persistence.createEntityManagerFactory(PERSISTENCE_UNIT_NAME);
			em = emf.createEntityManager();
		}
		return em;
	}
	
	/**
	 * Get the criteria builder 
	 * @return {@link CriteriaBuilder}
	 */
	protected CriteriaBuilder getCriteriaBuilder() {
		return getEm().getCriteriaBuilder();
	}
	
	/**
     * Determine the {@link ParameterizedType} of this repository. This allows us to detect with entity class this
     * repository is managing.
     * @return the class of the Entity this repository is managing
     */
    @SuppressWarnings("unchecked")
    private Class<T> determineGenericClass() {
        Class<T> result = null;
        Type genericSuperClass = this.getClass().getGenericSuperclass();
        
        if (genericSuperClass instanceof ParameterizedType) {
            ParameterizedType pt = (ParameterizedType) genericSuperClass;
            Type[] fieldArgTypes = pt.getActualTypeArguments();
            result = (Class<T>) fieldArgTypes[0];
        }
        
        return result;
    }
	
	/**
	 * Get all the entities of the repository.
	 * @return a {@link List} of entities
	 */
	public List<T> getAll() {
		CriteriaBuilder cb = getCriteriaBuilder();
		CriteriaQuery<T> query = cb.createQuery(entityClass);
		Root<T> root = query.from(entityClass);
		
		query.select(root);
		return getEm().createQuery(query).getResultList();
	}
	
	/**
	 * @param id - {@link Long} ID of the entity
	 * @return an Entity
	 */
	public T get(Long id) {
		if (id == null) {
			return null;
		}
		CriteriaBuilder cb = getCriteriaBuilder();
		CriteriaQuery<T> query = cb.createQuery(entityClass);
		Root<T> root = query.from(entityClass);
		
		query.where(cb.equal(root.get("id"),id));
		query.select(root);
		return getEm().createQuery(query).getSingleResult();
	}
	
	/**
	 * Persist a new entity 
	 * @param entity
	 */
	public T persist(T entity) {
		if (entity != null) {
			getEm().getTransaction().begin();
			getEm().persist(entity);
			getEm().getTransaction().commit();
			return entity;
		}
		return null;
	}
	
	/**
	 * Merge the entity with persistence context
	 * @param entity
	 */
	public void merge(T entity) {
		if (entity != null) {
			getEm().merge(entity);
		}
	}
	
	/**
	 * Delete the entity from the persistence context
	 * @param entity
	 */
	public void delete(T entity) {
		if (entity != null){
			getEm().getTransaction().begin();
			getEm().remove(entity);
			getEm().getTransaction().commit();
		}
	}
	
	/**
	 * Refresh the entity with the content of the databases, erasing its content.
	 * @param entity
	 */
	public void refresh(T entity) {
		if (entity != null) {
			getEm().refresh(entity);
		}
	}
}
